import { isVNode, Fragment, Comment, Text, VNode,  VNodeChild, createTextVNode, ComponentInternalInstance, ComponentPublicInstance, cloneVNode } from 'vue'
import { isOn, camelize, hyphenate } from '@vue/shared'
import isValid from '../isValid'

export const splitAttrs = attrs => {
  const allAttrs = Object.keys(attrs)
  const eventAttrs = {}
  const onEvents = {}
  const extraAttrs = {}
  for (let i = 0, l = allAttrs.length; i < l; i++) {
    const key = allAttrs[i]
    if (isOn(key)) {
      eventAttrs[key[2].toLowerCase() + key.slice(3)] = attrs[key]
      onEvents[key] = attrs[key]
    } else {
      extraAttrs[key] = attrs[key]
    }
  }
  return { onEvents, events: eventAttrs, extraAttrs }
}

const getVm = (ins): ComponentPublicInstance => ins?.proxy || ins
const getIns = (vm): ComponentInternalInstance => vm?.$ || vm
export const getEL = (ins): HTMLElement => ins instanceof Node ? ins : getVm(ins)?.$el

export function getProp<T, K extends keyof T>(props: T, prop: K): T[K] {
  return props[camelize(prop as string)] || props[hyphenate(prop as string)]
}

export function hasProp(ins, prop) {
  const props = getIns(ins).vnode.props
  return camelize(prop) in props || hyphenate(prop) in props
}

export const findDOMNode = (instance): HTMLElement => {
  let node = getEL(instance) || instance
  while (node && !node.tagName) node = node.nextSibling
  return node
}

export function isEmptyElement(c: VNode) {
  return (
    c.type === Comment ||
    (c.type === Fragment && c.children.length === 0) ||
    (c.type === Text && (c.children as string).trim() === '')
  )
}
export function filterEmpty(children = []) {
  const res = []
  children.forEach(child => {
    if (Array.isArray(child)) {
      res.push(...child)
    } else if (child.type === Fragment) {
      res.push(...child.children)
    } else {
      res.push(child)
    }
  })
  return res.filter(c => !isEmptyElement(c))
}

export const flattenChildren = (children, filterEmpty = true): VNode[] => {
  const temp = Array.isArray(children) ? children : [children]
  const res = []
  for (let i = 0; i < temp.length; i++) {
    const child = temp[i]
    if (Array.isArray(child)) {
      res.push(...flattenChildren(child, filterEmpty))
    } else if (isVNode(child)) {
      if (child.type === Fragment) {
        res.push(...flattenChildren(child.children, filterEmpty))
      } else if (filterEmpty && !isEmptyElement(child)) {
        res.push(child)
      } else if (!filterEmpty) {
        res.push(child)
      }
    } else if (isValid(child)) {
      if (!isEmptyElement) res.push(child)
    }
  }
  return res
}

export const getSlot = (self, name = 'default', options = {}): VNode[] => {
  let res
  if (typeof self === 'function') {
    res = self(options)
  } else {
    res = (getIns(self).slots || self)[name]?.(options)
  }
  return flattenChildren(res)
}

export const getComponent = (ins, prop = 'default', opts = ins) => {
  let props = getIns(ins).props || ins, slots = getIns(ins).slots
  const val = props[prop]
  return render((val != null && typeof val !== 'boolean') ? val : slots?.[prop], opts)
}

export const render = (content: unknown, ...args): VNode => {
  if (typeof content === 'function') {
    return content(...args)
  } else if (typeof content === 'object' && content) {
    return content as VNode
  } else if (isValid(content)) {
    return createTextVNode(String(content))
  } else {
    return undefined
  }
}

export function _cloneVNode<T, U>(vnode: VNode<T, U>, extraProps?, mergeRef?: boolean) {
  if (!vnode) return undefined;
  return cloneVNode<T, U>(vnode, extraProps, mergeRef);
}

export function isValidElement(element) {
  return isVNode(element) && typeof element.type !== 'symbol'
}

export function hasElemnt(element): boolean {
  if (element == null) {
    return false
  } else if (Array.isArray(element)) {
    return element.some(hasElemnt)
  } else {
    if (isVNode(element)) {
      if (typeof element.type === 'symbol') {
        return hasElemnt(element.children)
      } else {
        return true
      }
    }
    return typeof element === 'string' || typeof element === 'number'
  }
}